package es.upm.gib.mappingAPI2.generate;

import es.upm.gib.mappingAPI2.Restriction;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class ActionSequence {

    public enum ActionName{
        ACCEPT_VIEW,
        DISCARD_VIEW,
        GENERALIZE_VIEW,
        GENERALIZE_VIEW_INT,
        SPECIFY_VIEW,
        SPECIFY_VIEW_STRING,
        SPECIFY_VIEW_INT,
        SPECIFY_VIEW_INT_STRING,
        DISCARD_CLASS_INT,
        DISCARD_CLASS_INT_INT,
        SET_IDENTIFIER_INT,
        SET_IDENTIFIER_INT_INT,
        REMOVE_IDENTIFIER_INT,
        REMOVE_IDENTIFIER_INT_INT,
        SET_RESTRICTION_INT_RESTRICTION,
        SET_RESTRICTION_INT_INT_RESTRICTION,
        REMOVE_RESTRICTION_INT,
        REMOVE_RESTRICTION_INT_INT,
        DISCARD_PROPERTY_INT,
        DISCARD_PROPERTY_INT_INT
    }

    /**
     * An action is a class containing a name and a undefined number of parameters
     */
    private static class Action{

        private ActionName name;
        private ArrayList<String> parameters;

        public Action(){
            this.parameters = new ArrayList<>();
        }

        public void setName(ActionName name){
            this.name = name;
        }

        private ActionName getName() {
            return name;
        }

        public void addParameter(ArrayList<String> parameters){
            this.parameters = parameters;
        }

        private ArrayList<String> getParameters() {
            return parameters;
        }
    }

    private ArrayList<Action> currentViewActionSequence;
    private Map<Integer,ArrayList<Action>> viewActionSequence = new HashMap<>();
    int index;

    public ActionSequence(){
        currentViewActionSequence = new ArrayList<>();
        viewActionSequence = new HashMap<>();
        index = 0;
    }

    public void addActionToSequence(ActionName name, String... params){
        Action newAction = new Action();
        newAction.setName(name);
        ArrayList<String> parameters = new ArrayList<>();
        Collections.addAll(parameters, params);
        newAction.addParameter(parameters);
        currentViewActionSequence.add(newAction);
    }

    public void endViewSequence(){
        viewActionSequence.put(index++,currentViewActionSequence);
        currentViewActionSequence = new ArrayList<>();
    }

    public String serialize(){
        Document doc = new Document(new Element("saved_progress"));

        //Accepted and Discarded views
        for(ArrayList<Action> actionSequence : viewActionSequence.values()){
            Element viewElement = new Element("view");
            for(Action action : actionSequence){
                Element actionElement = new Element("action");
                actionElement.setAttribute("name",action.getName().toString());
                StringBuilder paramaters = new StringBuilder();
                for(String parameter : action.getParameters()){
                    paramaters.append(parameter).append("##");
                }
                if(paramaters.length()>0){
                    actionElement.setAttribute("parameters",paramaters.toString().substring(0,paramaters.length()-2));
                }
                viewElement.addContent(actionElement);
            }
            doc.getRootElement().addContent(viewElement);
        }

        //Current View
        Element viewElement = new Element("view");
        viewElement.setAttribute("current","true");
        for(Action action : currentViewActionSequence){
            Element actionElement = new Element("action");
            actionElement.setAttribute("name",action.getName().toString());
            StringBuilder paramaters = new StringBuilder();
            for(String parameter : action.getParameters()){
                paramaters.append(parameter).append("##");
            }
            if(paramaters.length()>0){
                actionElement.setAttribute("parameters",paramaters.toString().substring(0,paramaters.length()-2));
            }
            viewElement.addContent(actionElement);
        }
        doc.getRootElement().addContent(viewElement);

        XMLOutputter xmlOutput = new XMLOutputter();
        xmlOutput.setFormat(Format.getPrettyFormat());
        return xmlOutput.outputString(doc);
    }

    public void deSerialize(PhysicalViewGenerator PVG, String path) throws IOException, JDOMException {

        SAXBuilder builder = new SAXBuilder();
        Document currentViewDoc = builder.build(path);
        Element rootNode = currentViewDoc.getRootElement();

        for(Element view : rootNode.getChildren()){
            for(Element action : view.getChildren()){
                callAction(PVG,ActionName.valueOf(action.getAttributeValue("name")),action.getAttributeValue("parameters"));
            }
        }
    }


    private void callAction(PhysicalViewGenerator PVG, ActionName actionName, String params){
        String[] parameter = new String[0];
        if(params!=null)
            parameter = params.split("##");
        Restriction restriction;

        switch (actionName){
            case ACCEPT_VIEW:
                PVG.acceptView();
                break;
            case DISCARD_VIEW:
                PVG.discardView();
                break;
            case GENERALIZE_VIEW:
                PVG.generalizeView();
                break;
            case SPECIFY_VIEW:
                PVG.specifyView();
                break;
            case SPECIFY_VIEW_STRING:
                PVG.specifyView(params);
                break;
            case SET_RESTRICTION_INT_INT_RESTRICTION:
                restriction = new Restriction(Restriction.Operator.valueOf(parameter[2]),parameter[3],Restriction.Range.valueOf(parameter[4]));
                PVG.setRestriction(Integer.valueOf(parameter[0]),Integer.valueOf(parameter[1]),restriction);
                break;
            case SET_RESTRICTION_INT_RESTRICTION:
                restriction = new Restriction(Restriction.Operator.valueOf(parameter[1]),parameter[2],Restriction.Range.valueOf(parameter[3]));
                PVG.setRestriction(Integer.valueOf(parameter[0]),restriction);
                break;
            case GENERALIZE_VIEW_INT:
                PVG.generalizeView(Integer.valueOf(params));
                break;
            case REMOVE_RESTRICTION_INT:
                PVG.removeRestriction(Integer.valueOf(params));
                break;
            case REMOVE_RESTRICTION_INT_INT:
                PVG.removeRestriction(Integer.valueOf(parameter[0]),Integer.valueOf(parameter[1]));
                break;
            case SET_IDENTIFIER_INT:
                PVG.setIdentifier(Integer.valueOf(params));
                break;
            case REMOVE_IDENTIFIER_INT:
                PVG.removeIdentifier(Integer.valueOf(params));
                break;
            case SET_IDENTIFIER_INT_INT:
                PVG.setIdentifier(Integer.valueOf(parameter[0]),Integer.valueOf(parameter[1]));
                break;
            case REMOVE_IDENTIFIER_INT_INT:
                PVG.removeIdentifier(Integer.valueOf(parameter[0]), Integer.valueOf(parameter[1]));
                break;
            case SPECIFY_VIEW_INT:
                PVG.specifyView(Integer.valueOf(params));
                break;
            case SPECIFY_VIEW_INT_STRING:
                if(parameter[1].equals("null"))
                    PVG.specifyView(Integer.valueOf(parameter[0]),null);
                else
                    PVG.specifyView(Integer.valueOf(parameter[0]),parameter[1]);
                break;
            case DISCARD_CLASS_INT:
                PVG.discardClass(Integer.valueOf(params));
                break;
            case DISCARD_CLASS_INT_INT:
                PVG.discardClass(Integer.valueOf(parameter[0]),Integer.valueOf(parameter[1]));
                break;
            case DISCARD_PROPERTY_INT:
                PVG.discardProperty(Integer.valueOf(params));
                break;
            case DISCARD_PROPERTY_INT_INT:
                PVG.discardProperty(Integer.valueOf(parameter[0]),Integer.valueOf(parameter[1]));
                break;
        }
    }
}
